import sys
import os
import threading
import time
import logging
import socket
import subprocess
import re
import queue
from datetime import datetime
import requests
import uiautomator2 as u2
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys  # Добавлено для Keys.CONTROL + 'a'
from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException

from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                             QPushButton, QLabel, QTextEdit, QLineEdit, QCheckBox, QComboBox,
                             QGridLayout, QGroupBox, QFormLayout, QMessageBox, QMenu, QFrame,
                             QScrollArea, QSizePolicy, QProgressBar)
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal, QObject, QMutex
from PyQt5.QtGui import QFont, QColor, QTextCursor, QCursor

# Настройка логирования
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Глобальные переменные
successful_attempts = 0
failed_attempts = 0
batch_success_count = 0
pause_event = threading.Event()
pause_event.set()
is_processing = False
error_attempt_counter = 0
auto_paused_due_to_wifi = False

# Счетчики для различных типов ошибок
invalid_login_count = 0
captcha_count = 0
token_not_found_count = 0
timeout_count = 0
element_not_found_count = 0
unexpected_error_count = 0
sms_code_count = 0  # Новый счётчик для "Код из SMS"

all_results = []
file_lock = threading.Lock()

# Очереди для логов
log_queue = queue.Queue()
token_queue = queue.Queue()
failed_queue = queue.Queue()

# Пути к файлам
tokens_file = r"tokens.txt"
failed_logins_file = r"неуспешные логи.txt"
login_pass_file = r"log pass.txt"
driver_path = r"chromedriver_win64\chromedriver.exe"
last_limit_file = r"last limit.txt"
hide_browser_state_file = r"hide_browser_state.txt"
last_model_file = r"last_model.txt"
run_scenario_start_state_file = r"run_scenario_start_state.txt"
run_scenario_end_state_file = r"run_scenario_end_state.txt"
tokens_autoscroll_state_file = r"tokens_autoscroll_state.txt"
failed_autoscroll_state_file = r"failed_autoscroll_state.txt"

devices_by_model = {}
available_wifi_networks = []

# Функция для форматирования времени в формат ЧЧ:ММ:СС
def format_time(seconds):
    """Форматирует время из секунд в формат ЧЧ:ММ:СС"""
    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    seconds = int(seconds % 60)
    return f"{hours:02d}:{minutes:02d}:{seconds:02d}"

# Сигналы для обновления GUI из потоков
class Signals(QObject):
    log_message = pyqtSignal(str, str)  # message, msg_type
    update_labels = pyqtSignal()
    append_token = pyqtSignal(str)
    append_failed_login = pyqtSignal(str, str)
    wifi_status_updated = pyqtSignal(str)
    ip_location_updated = pyqtSignal(str, str)
    device_scan_completed = pyqtSignal(list)
    wifi_scan_completed = pyqtSignal(list, str)
    update_progress = pyqtSignal(int, int)  # current, total

signals = Signals()

# Поток для обработки основных логов
class LogWorker(QThread):
    log_ready = pyqtSignal(str, str)
    
    def __init__(self):
        super().__init__()
        self.running = True
        
    def run(self):
        while self.running:
            try:
                message, msg_type = log_queue.get(timeout=0.1)
                self.log_ready.emit(message, msg_type)
                log_queue.task_done()
            except queue.Empty:
                continue
            except Exception as e:
                print(f"Ошибка в LogWorker: {e}")
                
    def stop(self):
        self.running = False

# Поток для обработки токенов
class TokenWorker(QThread):
    token_ready = pyqtSignal(str)
    
    def __init__(self):
        super().__init__()
        self.running = True
        
    def run(self):
        while self.running:
            try:
                token = token_queue.get(timeout=0.1)
                self.token_ready.emit(token)
                token_queue.task_done()
            except queue.Empty:
                continue
            except Exception as e:
                print(f"Ошибка в TokenWorker: {e}")
                
    def stop(self):
        self.running = False

# Поток для обработки неуспешных логинов
class FailedWorker(QThread):
    failed_ready = pyqtSignal(str, str)
    
    def __init__(self):
        super().__init__()
        self.running = True
        
    def run(self):
        while self.running:
            try:
                login, password = failed_queue.get(timeout=0.1)
                self.failed_ready.emit(login, password)
                failed_queue.task_done()
            except queue.Empty:
                continue
            except Exception as e:
                print(f"Ошибка в FailedWorker: {e}")
                
    def stop(self):
        self.running = False

# Функции для добавления сообщений в очереди
def queue_log_message(message, msg_type="info"):
    try:
        log_queue.put_nowait((message, msg_type))
    except queue.Full:
        pass  # Игнорируем если очередь переполнена

def queue_token_message(token):
    try:
        token_queue.put_nowait(token)
    except queue.Full:
        pass

def queue_failed_message(login, password):
    try:
        failed_queue.put_nowait((login, password))
    except queue.Full:
        pass

# Рабочий поток для обработки аккаунтов
class ProcessingThread(QThread):
    def __init__(self, main_window):
        super().__init__()
        self.main_window = main_window
        self.current_account = 0
        self.total_accounts = 0

    def run(self):
        global is_processing
        try:
            if self.main_window.run_scenario_start_check.isChecked():
                queue_log_message("Запуск сценария в начале.", "info")
                self.main_window.run_flight_mode_scenario()
            self.process_all_accounts()
        except Exception as ex:
            queue_log_message(f"Ошибка в основном потоке: {ex}", "error")
        finally:
            is_processing = False

    def process_all_accounts(self):
        accounts = read_login_passwords()
        self.total_accounts = len(accounts)
        signals.update_progress.emit(0, self.total_accounts)
        
        for i, (login, password) in enumerate(accounts):
            pause_event.wait()
            if not is_processing:
                break
                
            self.current_account = i + 1
            signals.update_progress.emit(self.current_account, self.total_accounts)
            
            queue_log_message(f"Обработка аккаунта {self.current_account}/{self.total_accounts}: {login}", "info")
            self.main_window.open_browser(login, password)
        
        if is_processing and self.main_window.run_scenario_end_check.isChecked():
            queue_log_message("Запуск сценария в конце.", "info")
            self.main_window.run_flight_mode_scenario()

class ClickableLabel(QLabel):
    """Кликабельная метка с подчёркиванием"""
    clicked = pyqtSignal()
    
    def __init__(self, text="", parent=None):
        super().__init__(text, parent)
        self.setCursor(QCursor(Qt.PointingHandCursor))
        self.setStyleSheet("""
            QLabel {
                color: #b8c0d0;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffffff;
                background-color: rgba(62, 70, 86, 0.3);
                border-radius: 2px;
            }
        """)
    
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.clicked.emit()
        super().mousePressEvent(event)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.processing_thread = None
        self.timer_start_time = None
        self.total_elapsed_time = 0
        self.is_timer_running = False
        
        # Переменные для автопрокрутки
        self.tokens_autoscroll_enabled = True
        self.failed_autoscroll_enabled = True
        
        # Инициализация рабочих потоков для логов
        self.log_worker = LogWorker()
        self.token_worker = TokenWorker()
        self.failed_worker = FailedWorker()
        
        self.init_ui()
        self.center_window()
        self.setup_timers()
        self.connect_signals()
        self.setup_log_workers()
        self.load_settings()
        self.setup_context_menus()
        
    def setup_log_workers(self):
        """Настройка рабочих потоков для логов"""
        # Подключаем сигналы от рабочих потоков
        self.log_worker.log_ready.connect(self.log_message_direct)
        self.token_worker.token_ready.connect(self.append_token_direct)
        self.failed_worker.failed_ready.connect(self.append_failed_login_direct)
        
        # Запускаем рабочие потоки
        self.log_worker.start()
        self.token_worker.start()
        self.failed_worker.start()
        
    def center_window(self):
        """Центрирование окна на экране"""
        screen_geometry = QApplication.desktop().availableGeometry()
        screen_center_x = screen_geometry.width() // 2
        screen_center_y = screen_geometry.height() // 2
        
        window_width = 1200
        window_height = 950
        
        self.resize(window_width, window_height)
        
        x = screen_center_x - window_width // 2
        y = screen_center_y - window_height // 2
        
        self.move(x, y)
        
    def init_ui(self):
        self.setWindowTitle("Получение токенов ВК (PyQt5)")
        self.setStyleSheet("""
            QMainWindow {
                background-color: #1e222a;
                color: #b8c0d0;
            }
            QGroupBox {
                font-weight: bold;
                border: 2px solid #3e4656;
                border-radius: 5px;
                margin-top: 1ex;
                padding-top: 10px;
                background-color: #262a33;
                color: #b8c0d0;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
            }
            QPushButton {
                background-color: #3e4656;
                color: #b8c0d0;
                border: 1px solid #4e566a;
                padding: 8px;
                border-radius: 4px;
                font: 10pt "Segoe UI";
            }
            QPushButton:hover {
                background-color: #4e566a;
            }
            QPushButton:pressed {
                background-color: #2a2f38;
            }
            QPushButton:disabled {
                background-color: #2a2f38;
                color: #6b7280;
            }
            QLineEdit, QComboBox {
                background-color: #2a2f38;
                color: #b8c0d0;
                border: 1px solid #3e4656;
                padding: 5px;
                border-radius: 3px;
            }
            QTextEdit {
                background-color: #2a2f38;
                color: #b8c0d0;
                border: 1px solid #3e4656;
                border-radius: 3px;
            }
            QCheckBox {
                color: #b8c0d0;
            }
            QLabel {
                color: #b8c0d0;
            }
            QProgressBar {
                background-color: #2a2f38;
                border: 1px solid #3e4656;
                border-radius: 3px;
                text-align: center;
                color: #b8c0d0;
            }
            QProgressBar::chunk {
                background-color: #5b9bd5;
                border-radius: 2px;
            }
        """)
        
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(10)
        main_layout.setContentsMargins(10, 10, 10, 10)
        
        # Панель управления
        self.create_control_panel(main_layout)
        
        # Статистика
        self.create_status_panel(main_layout)
        
        # Прогресс бар
        self.create_progress_panel(main_layout)
        
        # Логи
        self.create_logs_panel(main_layout)
        
    def create_control_panel(self, parent_layout):
        control_group = QGroupBox("Панель управления")
        control_layout = QVBoxLayout(control_group)
        
        # Первая строка кнопок
        row1_layout = QHBoxLayout()
        
        self.start_button = QPushButton("Запустить")
        # Специальные стили для кнопки "Запустить" с динамическим hover
        self.start_button.setStyleSheet("""
            QPushButton {
                background-color: #5b9bd5;
                color: #b8c0d0;
                border: 1px solid #4e566a;
                padding: 8px;
                border-radius: 4px;
                font: 10pt "Segoe UI";
            }
            QPushButton:enabled:hover {
                background-color: #4a8bc2;
                border: 1px solid #5b9bd5;
                color: #ffffff;
            }
            QPushButton:enabled:pressed {
                background-color: #3a7ba8;
            }
            QPushButton:disabled {
                background-color: #2a2f38;
                color: #6b7280;
                border: 1px solid #3e4656;
            }
        """)
        self.start_button.clicked.connect(self.start_browser_thread)
        row1_layout.addWidget(self.start_button)
        
        self.pause_button = QPushButton("Приостановить")
        self.pause_button.setEnabled(False)
        self.pause_button.clicked.connect(self.pause_processing)
        row1_layout.addWidget(self.pause_button)
        
        self.resume_button = QPushButton("Восстановить")
        self.resume_button.setEnabled(False)
        self.resume_button.clicked.connect(self.resume_processing)
        row1_layout.addWidget(self.resume_button)
        
        self.restart_button = QPushButton("Перезапустить ошибки")
        self.restart_button.clicked.connect(self.restart_failed_accounts)
        row1_layout.addWidget(self.restart_button)
        
        self.update_labels_button = QPushButton("Обновить метки")
        self.update_labels_button.clicked.connect(self.update_status_labels)
        row1_layout.addWidget(self.update_labels_button)
        
        self.clear_logs_button = QPushButton("Очистить все журналы")
        self.clear_logs_button.clicked.connect(self.clear_all_logs)
        row1_layout.addWidget(self.clear_logs_button)
        
        row1_layout.addStretch()
        control_layout.addLayout(row1_layout)
        
        # Вторая строка - чекбоксы
        row2_layout = QHBoxLayout()
        
        self.hide_browser_check = QCheckBox("Скрыть браузер")
        row2_layout.addWidget(self.hide_browser_check)
        
        self.run_scenario_start_check = QCheckBox("Запустить сценарий в начале")
        row2_layout.addWidget(self.run_scenario_start_check)
        
        self.run_scenario_end_check = QCheckBox("Запустить сценарий в конце")
        row2_layout.addWidget(self.run_scenario_end_check)
        
        # Лимит токенов
        limit_label = QLabel("Лимит токенов:")
        row2_layout.addWidget(limit_label)
        
        self.limit_entry = QLineEdit()
        self.limit_entry.setMaximumWidth(60)
        self.limit_entry.setText("0")
        row2_layout.addWidget(self.limit_entry)
        
        row2_layout.addStretch()
        control_layout.addLayout(row2_layout)
        
        # Третья строка - устройства и Wi-Fi
        row3_layout = QHBoxLayout()
        
        self.device_combobox = QComboBox()
        self.device_combobox.addItem("Нет подключённых устройств")
        self.device_combobox.currentTextChanged.connect(self.on_model_selected)
        row3_layout.addWidget(self.device_combobox)
        
        self.scan_devices_button = QPushButton("Сканировать устройства")
        self.scan_devices_button.clicked.connect(self.scan_devices_async)
        row3_layout.addWidget(self.scan_devices_button)
        
        wifi_label = QLabel("Выбрать Wi-Fi:")
        row3_layout.addWidget(wifi_label)
        
        self.wifi_combobox = QComboBox()
        self.wifi_combobox.setMinimumWidth(200)  # Устанавливаем минимальную ширину
        self.wifi_combobox.currentTextChanged.connect(self.on_wifi_selected)
        row3_layout.addWidget(self.wifi_combobox)
        
        self.refresh_wifi_button = QPushButton("Обновить Wi-Fi")
        self.refresh_wifi_button.clicked.connect(self.refresh_wifi_async)
        row3_layout.addWidget(self.refresh_wifi_button)
        
        row3_layout.addStretch()
        control_layout.addLayout(row3_layout)
        
        parent_layout.addWidget(control_group)
        
    def create_status_panel(self, parent_layout):
        status_group = QGroupBox("Статус")
        status_layout = QVBoxLayout(status_group)
        
        # Основная статистика
        stats_layout = QHBoxLayout()
        
        # Используем ClickableLabel для кликабельных меток
        self.success_label = ClickableLabel("Успешные попытки: 0")
        self.success_label.setStyleSheet("""
            QLabel {
                color: #89ca78;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #a4d9a0;
                background-color: rgba(137, 202, 120, 0.2);
                border-radius: 2px;
            }
        """)
        self.success_label.clicked.connect(self.copy_pure_successful_tokens)
        stats_layout.addWidget(self.success_label)
        
        self.failure_label = ClickableLabel("Неуспешные попытки: 0")
        self.failure_label.setStyleSheet("""
            QLabel {
                color: #e57373;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffaaaa;
                background-color: rgba(229, 115, 115, 0.2);
                border-radius: 2px;
            }
        """)
        self.failure_label.clicked.connect(self.copy_failed_logins)
        stats_layout.addWidget(self.failure_label)
        
        self.count_label = QLabel("Всего аккаунтов: 0")
        self.count_label.setStyleSheet("color: #5b9bd5;")
        stats_layout.addWidget(self.count_label)
        
        self.remaining_label = QLabel("Оставшиеся аккаунты: 0")
        self.remaining_label.setStyleSheet("color: #f4a261;")
        stats_layout.addWidget(self.remaining_label)
        
        copy_all_label = ClickableLabel("Copy all")
        copy_all_label.setStyleSheet("""
            QLabel {
                color: #b8c0d0;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffffff;
                background-color: rgba(184, 192, 208, 0.2);
                border-radius: 2px;
            }
        """)
        copy_all_label.clicked.connect(self.copy_all)
        stats_layout.addWidget(copy_all_label)
        
        copy_successful_label = ClickableLabel("Copy successful")
        copy_successful_label.setStyleSheet("""
            QLabel {
                color: #89ca78;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #a4d9a0;
                background-color: rgba(137, 202, 120, 0.2);
                border-radius: 2px;
            }
        """)
        copy_successful_label.clicked.connect(self.copy_successful_only)
        stats_layout.addWidget(copy_successful_label)
        
        stats_layout.addStretch()
        status_layout.addLayout(stats_layout)
        
        # Счетчики ошибок - первая строка (КЛИКАБЕЛЬНЫЕ)
        error_layout1 = QHBoxLayout()
        
        self.invalid_login_label = ClickableLabel("Невалидный логин/пароль: 0")
        self.invalid_login_label.setStyleSheet("""
            QLabel {
                color: #ff6b6b;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ff9999;
                background-color: rgba(255, 107, 107, 0.2);
                border-radius: 2px;
            }
        """)
        self.invalid_login_label.clicked.connect(self.copy_invalid_login_errors)
        error_layout1.addWidget(self.invalid_login_label)
        
        self.captcha_label = ClickableLabel("Капча: 0")
        self.captcha_label.setStyleSheet("""
            QLabel {
                color: #ff9f43;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffb366;
                background-color: rgba(255, 159, 67, 0.2);
                border-radius: 2px;
            }
        """)
        self.captcha_label.clicked.connect(self.copy_captcha_errors)
        error_layout1.addWidget(self.captcha_label)
        
        self.token_not_found_label = ClickableLabel("Токен не найден: 0")
        self.token_not_found_label.setStyleSheet("""
            QLabel {
                color: #feca57;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffd770;
                background-color: rgba(254, 202, 87, 0.2);
                border-radius: 2px;
            }
        """)
        self.token_not_found_label.clicked.connect(self.copy_token_not_found_errors)
        error_layout1.addWidget(self.token_not_found_label)
        
        # Новая метка для "Код из SMS"
        self.sms_code_label = ClickableLabel("Код из SMS: 0")
        self.sms_code_label.setStyleSheet("""
            QLabel {
                color: #ffcc00;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ffe066;
                background-color: rgba(255, 204, 0, 0.2);
                border-radius: 2px;
            }
        """)
        self.sms_code_label.clicked.connect(self.copy_sms_code_errors)
        error_layout1.addWidget(self.sms_code_label)
        
        error_layout1.addStretch()
        status_layout.addLayout(error_layout1)
        
        # Счетчики ошибок - вторая строка (КЛИКАБЕЛЬНЫЕ)
        error_layout2 = QHBoxLayout()
        
        self.timeout_label = ClickableLabel("Timeout: 0")
        self.timeout_label.setStyleSheet("""
            QLabel {
                color: #ff7675;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ff9999;
                background-color: rgba(255, 118, 117, 0.2);
                border-radius: 2px;
            }
        """)
        self.timeout_label.clicked.connect(self.copy_timeout_errors)
        error_layout2.addWidget(self.timeout_label)
        
        self.element_not_found_label = ClickableLabel("Элемент не найден: 0")
        self.element_not_found_label.setStyleSheet("""
            QLabel {
                color: #a29bfe;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #b8b3ff;
                background-color: rgba(162, 155, 254, 0.2);
                border-radius: 2px;
            }
        """)
        self.element_not_found_label.clicked.connect(self.copy_element_not_found_errors)
        error_layout2.addWidget(self.element_not_found_label)
        
        self.unexpected_error_label = ClickableLabel("Неожиданные ошибки: 0")
        self.unexpected_error_label.setStyleSheet("""
            QLabel {
                color: #fd79a8;
                font-size: 9pt;
                text-decoration: underline;
                padding: 2px;
            }
            QLabel:hover {
                color: #ff99cc;
                background-color: rgba(253, 121, 168, 0.2);
                border-radius: 2px;
            }
        """)
        self.unexpected_error_label.clicked.connect(self.copy_unexpected_errors)
        error_layout2.addWidget(self.unexpected_error_label)
        
        error_layout2.addStretch()
        status_layout.addLayout(error_layout2)
        
        # Дополнительная информация
        info_layout = QHBoxLayout()
        
        self.total_time_label = QLabel("Общее время: 00:00:00")
        self.total_time_label.setStyleSheet("color: #f4a261;")
        info_layout.addWidget(self.total_time_label)
        
        self.wifi_status_label = QLabel("Подключенный Wi-Fi: Неизвестно")
        self.wifi_status_label.setStyleSheet("color: #bb77d1;")
        info_layout.addWidget(self.wifi_status_label)
        
        self.ip_label = QLabel("IP: Сканирование...")
        info_layout.addWidget(self.ip_label)
        
        self.location_label = QLabel("Локация: Сканирование...")
        info_layout.addWidget(self.location_label)
        
        info_layout.addStretch()
        status_layout.addLayout(info_layout)
        
        parent_layout.addWidget(status_group)
        
    def create_progress_panel(self, parent_layout):
        """Создание панели прогресса"""
        progress_group = QGroupBox("Прогресс выполнения")
        progress_layout = QVBoxLayout(progress_group)
        
        # Основной прогресс бар
        self.progress_bar = QProgressBar()
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(100)
        self.progress_bar.setValue(0)
        self.progress_bar.setFormat("Ожидание запуска...")
        progress_layout.addWidget(self.progress_bar)
        
        # Информация о прогрессе
        progress_info_layout = QHBoxLayout()
        
        self.progress_label = QLabel("Готов к запуску")
        self.progress_label.setStyleSheet("color: #b8c0d0;")
        progress_info_layout.addWidget(self.progress_label)
        
        progress_info_layout.addStretch()
        progress_layout.addLayout(progress_info_layout)
        
        parent_layout.addWidget(progress_group)
        
    def create_logs_panel(self, parent_layout):
        logs_layout = QHBoxLayout()
        
        # Журнал действий
        log_group = QGroupBox("Журнал действий")
        log_layout = QVBoxLayout(log_group)
        
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setMinimumHeight(300)
        log_layout.addWidget(self.log_text)
        logs_layout.addWidget(log_group)
        
        # Успешные токены
        tokens_group = QGroupBox("Успешные токены")
        tokens_layout = QVBoxLayout(tokens_group)
        
        self.successful_tokens_text = QTextEdit()
        self.successful_tokens_text.setReadOnly(True)
        self.successful_tokens_text.setMinimumHeight(300)
        tokens_layout.addWidget(self.successful_tokens_text)
        logs_layout.addWidget(tokens_group)
        
        parent_layout.addLayout(logs_layout)
        
        # Неуспешные логины
        failed_group = QGroupBox("Неуспешные логины и пароли")
        failed_layout = QVBoxLayout(failed_group)
        
        self.failed_logins_text = QTextEdit()
        self.failed_logins_text.setReadOnly(True)
        self.failed_logins_text.setMaximumHeight(150)
        failed_layout.addWidget(self.failed_logins_text)
        
        parent_layout.addWidget(failed_group)
        
    def setup_timers(self):
        # Таймер для обновления времени работы
        self.runtime_timer = QTimer()
        self.runtime_timer.timeout.connect(self.update_runtime_display)
        
        # Таймер для проверки Wi-Fi
        self.wifi_timer = QTimer()
        self.wifi_timer.timeout.connect(self.update_wifi_status)
        self.wifi_timer.start(2000)  # Каждые 2 секунды
        
        # Таймер для обновления IP и локации
        self.ip_timer = QTimer()
        self.ip_timer.timeout.connect(self.update_ip_location_display)
        self.ip_timer.start(15000)  # Каждые 15 секунд
        
    def setup_context_menus(self):
        # Контекстное меню для логов
        self.log_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.log_text.customContextMenuRequested.connect(self.show_log_context_menu)
        
        self.successful_tokens_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.successful_tokens_text.customContextMenuRequested.connect(self.show_tokens_context_menu)
        
        self.failed_logins_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.failed_logins_text.customContextMenuRequested.connect(self.show_failed_context_menu)
        
    def connect_signals(self):
        signals.log_message.connect(self.queue_log_message_wrapper)
        signals.update_labels.connect(self.update_status_labels)
        signals.append_token.connect(self.queue_token_wrapper)
        signals.append_failed_login.connect(self.queue_failed_wrapper)
        signals.wifi_status_updated.connect(self.wifi_status_label.setText)
        signals.ip_location_updated.connect(self.update_ip_location_labels)
        signals.device_scan_completed.connect(self.update_device_list)
        signals.wifi_scan_completed.connect(self.update_wifi_list)
        signals.update_progress.connect(self.update_progress_bar)
        
    def update_progress_bar(self, current, total):
        """Обновление прогресс бара"""
        if total > 0:
            percentage = int((current / total) * 100)
            self.progress_bar.setValue(percentage)
            self.progress_bar.setFormat(f"{current}/{total} ({percentage}%)")
            self.progress_label.setText(f"Обработано: {current} из {total} аккаунтов")
        else:
            self.progress_bar.setValue(0)
            self.progress_bar.setFormat("Ожидание...")
            self.progress_label.setText("Готов к запуску")
        
    def queue_log_message_wrapper(self, message, msg_type):
        """Обертка для добавления лог-сообщений в очередь"""
        queue_log_message(message, msg_type)
        
    def queue_token_wrapper(self, token):
        """Обертка для добавления токенов в очередь"""
        queue_token_message(token)
        
    def queue_failed_wrapper(self, login, password):
        """Обертка для добавления неуспешных логинов в очередь"""
        queue_failed_message(login, password)
        
    def log_message_direct(self, message, msg_type="info"):
        """Прямое добавление сообщения в лог (вызывается из рабочего потока)"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        full_message = f"[{timestamp}] {message}"
        
        cursor = self.log_text.textCursor()
        cursor.movePosition(QTextCursor.End)
        self.log_text.setTextCursor(cursor)
        
        if msg_type == "info":
            self.log_text.setTextColor(QColor("#b8c0d0"))
        elif msg_type == "success":
            self.log_text.setTextColor(QColor("#89ca78"))
        elif msg_type == "error":
            self.log_text.setTextColor(QColor("#e57373"))
        
        self.log_text.insertPlainText(full_message + "\n")
        self.log_text.ensureCursorVisible()
        
    def append_token_direct(self, token):
        """Прямое добавление токена (вызывается из рабочего потока)"""
        self.successful_tokens_text.append(token)
        if self.tokens_autoscroll_enabled:
            self.successful_tokens_text.ensureCursorVisible()
        
    def append_failed_login_direct(self, login, password):
        """Прямое добавление неуспешного логина (вызывается из рабочего потока)"""
        self.failed_logins_text.append(f"{login}:{password}")
        if self.failed_autoscroll_enabled:
            self.failed_logins_text.ensureCursorVisible()
            
    def load_settings(self):
        # Загрузка сохраненных настроек
        self.hide_browser_check.setChecked(self.load_hide_browser_state())
        self.run_scenario_start_check.setChecked(self.load_run_scenario_start_state())
        self.run_scenario_end_check.setChecked(self.load_run_scenario_end_state())
        self.limit_entry.setText(self.load_last_limit())
        
        # Загрузка состояния автопрокрутки
        self.tokens_autoscroll_enabled = self.load_tokens_autoscroll_state()
        self.failed_autoscroll_enabled = self.load_failed_autoscroll_state()
        
        # Инициализация
        queue_log_message("Журнал действий:", "info")
        self.update_status_labels()
        self.scan_devices_async()
        self.refresh_wifi_async()
        self.update_ip_location_display()
        
    def reset_timer(self):
        """Сброс таймера до нуля"""
        self.total_elapsed_time = 0
        self.timer_start_time = None
        self.is_timer_running = False
        self.runtime_timer.stop()
        self.total_time_label.setText("Общее время: 00:00:00")
        queue_log_message("Таймер сброшен", "info")
        
    def update_status_labels(self):
        accounts = read_login_passwords()
        total_accounts = len(accounts)
        remaining = total_accounts - (successful_attempts + failed_attempts)
        
        self.success_label.setText(f"Успешные попытки: {successful_attempts}")
        self.failure_label.setText(f"Неуспешные попытки: {failed_attempts}")
        self.count_label.setText(f"Всего аккаунтов: {total_accounts}")
        self.remaining_label.setText(f"Оставшиеся аккаунты: {remaining}")
        
        # Обновляем счетчики ошибок
        self.invalid_login_label.setText(f"Невалидный логин/пароль: {invalid_login_count}")
        self.captcha_label.setText(f"Капча: {captcha_count}")
        self.token_not_found_label.setText(f"Токен не найден: {token_not_found_count}")
        self.timeout_label.setText(f"Timeout: {timeout_count}")
        self.element_not_found_label.setText(f"Элемент не найден: {element_not_found_count}")
        self.unexpected_error_label.setText(f"Неожиданные ошибки: {unexpected_error_count}")
        self.sms_code_label.setText(f"Код из SMS: {sms_code_count}")  # Новая метка
        
    def start_browser_thread(self):
        global is_processing
        if is_processing:
            QMessageBox.information(self, "Информация", "Процесс уже запущен.")
            return
            
        add_token_separator()
        
        if self.wifi_combobox.currentText() != get_connected_wifi():
            QMessageBox.warning(self, "Внимание", 
                               "Текущая Wi-Fi сеть не совпадает с выбранной.\nЗапуск невозможен.")
            return
            
        self.save_last_limit()
        is_processing = True
        pause_event.set()
        
        self.start_button.setEnabled(False)
        self.pause_button.setEnabled(True)
        self.resume_button.setEnabled(False)
        
        # Сброс прогресс бара
        self.progress_bar.setValue(0)
        self.progress_bar.setFormat("Инициализация...")
        self.progress_label.setText("Подготовка к запуску...")
        
        # *** ДОБАВЛЕНО: Сброс таймера только при запуске новой сессии ***
        self.reset_timer()
        
        # Запуск таймера
        self.start_timer()
        
        # Запуск обработки в отдельном потоке
        self.processing_thread = ProcessingThread(self)
        self.processing_thread.finished.connect(self.on_processing_finished)
        self.processing_thread.start()
        
    def pause_processing(self):
        global auto_paused_due_to_wifi
        if not is_processing:
            return
        pause_event.clear()
        auto_paused_due_to_wifi = False
        queue_log_message("Процесс приостановлен (вручную).", "info")
        self.pause_button.setEnabled(False)
        self.resume_button.setEnabled(True)
        self.stop_timer()
        
    def resume_processing(self):
        if not is_processing:
            return
        if self.wifi_combobox.currentText() != get_connected_wifi():
            QMessageBox.warning(self, "Внимание", 
                               "Невозможно возобновить: текущая Wi-Fi сеть не совпадает с выбранной.")
            return
        pause_event.set()
        queue_log_message("Процесс восстановлен (вручную).", "info")
        self.pause_button.setEnabled(True)
        self.resume_button.setEnabled(False)
        self.start_timer()
        
    def on_processing_finished(self):
        global is_processing
        is_processing = False
        self.start_button.setEnabled(True)
        self.pause_button.setEnabled(False)
        self.resume_button.setEnabled(False)
        self.stop_timer()
        
        # Завершение прогресс бара
        self.progress_bar.setFormat("Завершено")
        self.progress_label.setText("Обработка завершена")
        
    def restart_failed_accounts(self):
        global successful_attempts, failed_attempts, batch_success_count
        global invalid_login_count, captcha_count, token_not_found_count
        global timeout_count, element_not_found_count, unexpected_error_count, sms_code_count
        
        # Сброс всех счетчиков
        successful_attempts = 0
        failed_attempts = 0
        batch_success_count = 0
        invalid_login_count = 0
        captcha_count = 0
        token_not_found_count = 0
        timeout_count = 0
        element_not_found_count = 0
        unexpected_error_count = 0
        sms_code_count = 0  # Сброс нового счётчика
        
        all_results.clear()
        self.update_status_labels()
        self.successful_tokens_text.clear()
        
        content = self.failed_logins_text.toPlainText().strip()
        if not content:
            QMessageBox.information(self, "Информация", "Нет аккаунтов для перезапуска.")
            return
            
        self.failed_logins_text.clear()
        
        try:
            with open(failed_logins_file, "w", encoding="utf-8") as f:
                f.write("")
        except Exception as e:
            queue_log_message(f"Ошибка при очистке файла неуспешных логинов: {e}", "error")
            
        def process_failed():
            lines = content.splitlines()
            for line in lines:
                parts = line.split(":", 1)
                if len(parts) == 2:
                    login, password = parts
                    queue_log_message(f"Перезапуск аккаунта: {login}", "info")
                    self.open_browser(login, password)
            signals.update_labels.emit()
            
        thread = threading.Thread(target=process_failed, daemon=True)
        thread.start()
        
    def clear_all_logs(self):
        reply = QMessageBox.question(self, "Подтверждение", 
                                   "Вы уверены, что хотите очистить журналы в интерфейсе?\n(Файлы tokens.txt и неуспешные логи.txt не будут затронуты)",
                                   QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            try:
                self.log_text.clear()
                self.successful_tokens_text.clear()
                self.failed_logins_text.clear()
            
                global successful_attempts, failed_attempts
                global invalid_login_count, captcha_count, token_not_found_count
                global timeout_count, element_not_found_count, unexpected_error_count, sms_code_count
            
                successful_attempts = 0
                failed_attempts = 0
                invalid_login_count = 0
                captcha_count = 0
                token_not_found_count = 0
                timeout_count = 0
                element_not_found_count = 0
                unexpected_error_count = 0
                sms_code_count = 0  # Сброс нового счётчика
            
                all_results.clear()
                self.update_status_labels()
                
                # *** ИЗМЕНЕНО: Таймер не сбрасывается при очистке логов, только если он не работает ***
                if not self.is_timer_running:
                    self.reset_timer()
                
                # Сброс прогресс бара
                self.progress_bar.setValue(0)
                self.progress_bar.setFormat("Ожидание запуска...")
                self.progress_label.setText("Готов к запуску")
                
                queue_log_message("Журналы интерфейса очищены (файлы tokens.txt и неуспешные логи.txt сохранены).", "info")
            except Exception as e:
                QMessageBox.critical(self, "Ошибка", f"Не удалось очистить журналы: {e}")
        
    def copy_pure_successful_tokens(self):
        """Копирование только чистых успешных токенов (без ошибок)"""
        if not all_results:
            queue_log_message("Нет данных для копирования успешных токенов.", "info")
            return
            
        error_keywords = [
            "Невалидный логин или пароль",
            "Найдена капча", 
            "Токен не найден",
            "Timeout",
            "Элемент не найден",
            "Ошибка:",
            "Неожиданная ошибка",
            "Неизвестная ошибка",
            "Код из SMS"  # Добавлено для новой ошибки
        ]
        
        successful_tokens = []
        for login, password, result in all_results:
            is_error = any(keyword in result for keyword in error_keywords)
            # Проверяем что это токен (длинный и без ошибок)
            if not is_error and len(result) > 30 and ":" not in result:
                successful_tokens.append(result)
                
        if not successful_tokens:
            queue_log_message("Нет чистых успешных токенов для копирования.", "info")
            return
            
        text = "\n".join(successful_tokens)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(successful_tokens)} чистых успешных токенов в буфер обмена.", "info")
        
    def copy_failed_logins(self):
        content = self.failed_logins_text.toPlainText().strip()
        if not content:
            QMessageBox.information(self, "Информация", "Нет неуспешных логинов для копирования.")
            return
        QApplication.clipboard().setText(content)
        queue_log_message("Неуспешные логины и пароли скопированы в буфер обмена.", "info")
        
    def copy_all(self):
        if not all_results:
            QMessageBox.information(self, "Информация", "Нет данных для копирования.")
            return
            
        lines = [f"{login}:{password}\t{result}" for login, password, result in all_results]
        text = "\n".join(lines)
        QApplication.clipboard().setText(text)
        queue_log_message("Список логин:пароль и результатов скопирован.", "info")
        
    def copy_successful_only(self):
        if not all_results:
            QMessageBox.information(self, "Информация", "Нет данных для копирования.")
            return
            
        error_keywords = [
            "Невалидный логин или пароль",
            "Найдена капча", 
            "Токен не найден",
            "Timeout",
            "Элемент не найден",
            "Ошибка:",
            "Неожиданная ошибка",
            "Неизвестная ошибка",
            "Код из SMS"  # Добавлено для новой ошибки
        ]
        
        successful_results = []
        for login, password, result in all_results:
            is_error = any(keyword in result for keyword in error_keywords)
            if not is_error and len(result) > 10:
                successful_results.append((login, password, result))
                
        if not successful_results:
            QMessageBox.information(self, "Информация", "Нет успешных результатов для копирования.")
            return
            
        lines = [f"{login}:{password}\t{result}" for login, password, result in successful_results]
        text = "\n".join(lines)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(successful_results)} успешных результатов в буфер обмена.", "info")
        
    # МЕТОДЫ ДЛЯ КОПИРОВАНИЯ ОШИБОК ПО ТИПАМ
    def copy_invalid_login_errors(self):
        """Копирование ошибок невалидного логина/пароля"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок невалидного логина.", "info")
            return
            
        invalid_results = []
        for login, password, result in all_results:
            if "Невалидный логин или пароль" in result:
                invalid_results.append(f"{login}:{password}\t{result}")
                
        if not invalid_results:
            queue_log_message("Нет ошибок невалидного логина/пароля для копирования.", "info")
            return
            
        text = "\n".join(invalid_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(invalid_results)} ошибок невалидного логина/пароля.", "info")
        
    def copy_captcha_errors(self):
        """Копирование ошибок капчи"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок капчи.", "info")
            return
            
        captcha_results = []
        for login, password, result in all_results:
            if "Найдена капча" in result or "капча" in result.lower():
                captcha_results.append(f"{login}:{password}\t{result}")
                
        if not captcha_results:
            queue_log_message("Нет ошибок капчи для копирования.", "info")
            return
            
        text = "\n".join(captcha_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(captcha_results)} ошибок капчи.", "info")
        
    def copy_token_not_found_errors(self):
        """Копирование ошибок 'токен не найден'"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок 'токен не найден'.", "info")
            return
            
        token_not_found_results = []
        for login, password, result in all_results:
            if "Токен не найден" in result:
                token_not_found_results.append(f"{login}:{password}\t{result}")
                
        if not token_not_found_results:
            queue_log_message("Нет ошибок 'токен не найден' для копирования.", "info")
            return
            
        text = "\n".join(token_not_found_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(token_not_found_results)} ошибок 'токен не найден'.", "info")
        
    def copy_timeout_errors(self):
        """Копирование ошибок timeout"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок timeout.", "info")
            return
            
        timeout_results = []
        for login, password, result in all_results:
            if "Timeout" in result:
                timeout_results.append(f"{login}:{password}\t{result}")
                
        if not timeout_results:
            queue_log_message("Нет ошибок timeout для копирования.", "info")
            return
            
        text = "\n".join(timeout_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(timeout_results)} ошибок timeout.", "info")
        
    def copy_element_not_found_errors(self):
        """Копирование ошибок 'элемент не найден'"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок 'элемент не найден'.", "info")
            return
            
        element_not_found_results = []
        for login, password, result in all_results:
            if "Элемент не найден" in result:
                element_not_found_results.append(f"{login}:{password}\t{result}")
                
        if not element_not_found_results:
            queue_log_message("Нет ошибок 'элемент не найден' для копирования.", "info")
            return
            
        text = "\n".join(element_not_found_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(element_not_found_results)} ошибок 'элемент не найден'.", "info")
        
    def copy_unexpected_errors(self):
        """Копирование неожиданных ошибок"""
        if not all_results:
            queue_log_message("Нет данных для копирования неожиданных ошибок.", "info")
            return
            
        unexpected_results = []
        for login, password, result in all_results:
            if ("Ошибка:" in result or "Неожиданная ошибка" in result or "Неизвестная ошибка" in result) and \
               "Невалидный логин или пароль" not in result and \
               "Найдена капча" not in result and \
               "Токен не найден" not in result and \
               "Timeout" not in result and \
               "Элемент не найден" not in result and \
               "Код из SMS" not in result:
                unexpected_results.append(f"{login}:{password}\t{result}")
                
        if not unexpected_results:
            queue_log_message("Нет неожиданных ошибок для копирования.", "info")
            return
            
        text = "\n".join(unexpected_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(unexpected_results)} неожиданных ошибок.", "info")
        
    def copy_sms_code_errors(self):
        """Копирование ошибок 'Код из SMS' (новый метод)"""
        if not all_results:
            queue_log_message("Нет данных для копирования ошибок 'Код из SMS'.", "info")
            return
            
        sms_results = []
        for login, password, result in all_results:
            if "Код из SMS" in result:
                sms_results.append(f"{login}:{password}\t{result}")
                
        if not sms_results:
            queue_log_message("Нет ошибок 'Код из SMS' для копирования.", "info")
            return
            
        text = "\n".join(sms_results)
        QApplication.clipboard().setText(text)
        queue_log_message(f"Скопировано {len(sms_results)} ошибок 'Код из SMS'.", "info")
        
    def show_log_context_menu(self, pos):
        menu = QMenu()
        copy_action = menu.addAction("Копировать")
        select_all_action = menu.addAction("Выбрать всё")
        menu.addSeparator()
        autoscroll_action = menu.addAction("Автопрокрутка")
        autoscroll_action.setCheckable(True)
        autoscroll_action.setChecked(True)
        
        action = menu.exec_(self.log_text.mapToGlobal(pos))
        if action == copy_action:
            cursor = self.log_text.textCursor()
            if cursor.hasSelection():
                QApplication.clipboard().setText(cursor.selectedText())
        elif action == select_all_action:
            self.log_text.selectAll()
            
    def show_tokens_context_menu(self, pos):
        menu = QMenu()
        copy_action = menu.addAction("Копировать")
        select_all_action = menu.addAction("Выбрать всё")
        menu.addSeparator()
        autoscroll_action = menu.addAction("Автопрокрутка")
        autoscroll_action.setCheckable(True)
        autoscroll_action.setChecked(self.tokens_autoscroll_enabled)
        
        action = menu.exec_(self.successful_tokens_text.mapToGlobal(pos))
        if action == copy_action:
            cursor = self.successful_tokens_text.textCursor()
            if cursor.hasSelection():
                QApplication.clipboard().setText(cursor.selectedText())
        elif action == select_all_action:
            self.successful_tokens_text.selectAll()
        elif action == autoscroll_action:
            self.tokens_autoscroll_enabled = not self.tokens_autoscroll_enabled
            self.save_tokens_autoscroll_state()
            if self.tokens_autoscroll_enabled:
                queue_log_message("Автопрокрутка для токенов включена.", "info")
            else:
                queue_log_message("Автопрокрутка для токенов отключена.", "info")
                
    def show_failed_context_menu(self, pos):
        menu = QMenu()
        copy_action = menu.addAction("Копировать")
        select_all_action = menu.addAction("Выбрать всё")
        menu.addSeparator()
        autoscroll_action = menu.addAction("Автопрокрутка")
        autoscroll_action.setCheckable(True)
        autoscroll_action.setChecked(self.failed_autoscroll_enabled)
        
        action = menu.exec_(self.failed_logins_text.mapToGlobal(pos))
        if action == copy_action:
            cursor = self.failed_logins_text.textCursor()
            if cursor.hasSelection():
                QApplication.clipboard().setText(cursor.selectedText())
        elif action == select_all_action:
            self.failed_logins_text.selectAll()
        elif action == autoscroll_action:
            self.failed_autoscroll_enabled = not self.failed_autoscroll_enabled
            self.save_failed_autoscroll_state()
            if self.failed_autoscroll_enabled:
                queue_log_message("Автопрокрутка для неуспешных логинов включена.", "info")
            else:
                queue_log_message("Автопрокрутка для неуспешных логинов отключена.", "info")
            
    def start_timer(self):
        self.timer_start_time = time.time()
        self.is_timer_running = True
        self.runtime_timer.start(1000)  # Обновление каждую секунду
        
    def stop_timer(self):
        if self.is_timer_running and self.timer_start_time:
            self.total_elapsed_time += time.time() - self.timer_start_time
            self.is_timer_running = False
            self.runtime_timer.stop()
            
    def update_runtime_display(self):
        """Обновление отображения времени в формате ЧЧ:ММ:СС"""
        if self.is_timer_running and self.timer_start_time:
            current_time = self.total_elapsed_time + (time.time() - self.timer_start_time)
            formatted_time = format_time(current_time)
            self.total_time_label.setText(f"Время выполнения: {formatted_time}")
        else:
            formatted_time = format_time(self.total_elapsed_time)
            self.total_time_label.setText(f"Общее время: {formatted_time}")
            
    def update_wifi_status(self):
        current_wifi = get_connected_wifi()
        self.wifi_status_label.setText(f"Подключенный Wi-Fi: {current_wifi}")
        
        # Логика автопаузы при смене Wi-Fi
        selected_wifi = self.wifi_combobox.currentText()
        global auto_paused_due_to_wifi
        
        if selected_wifi and selected_wifi != current_wifi:
            if is_processing and pause_event.is_set():
                pause_event.clear()
                auto_paused_due_to_wifi = True
                queue_log_message("Процесс приостановлен автоматически (Wi-Fi mismatch).", "info")
            if not is_processing:
                self.start_button.setEnabled(False)
        else:
            if not is_processing:
                self.start_button.setEnabled(True)
            else:
                if auto_paused_due_to_wifi and not pause_event.is_set():
                    pause_event.set()
                    auto_paused_due_to_wifi = False
                    queue_log_message("Процесс возобновлён автоматически (Wi-Fi соответствует).", "info")
                    
    def update_ip_location_display(self):
        def worker():
            ip = get_external_ip()
            if ip != "Недоступно":
                location = get_location_by_ip(ip)
            else:
                location = "Недоступно"
            signals.ip_location_updated.emit(ip, location)
            
        thread = threading.Thread(target=worker, daemon=True)
        thread.start()
        
    def update_ip_location_labels(self, ip, location):
        self.ip_label.setText(f"IP: {ip}")
        self.location_label.setText(f"Локация: {location}")
        
    def scan_devices_async(self):
        def worker():
            global devices_by_model
            devices_by_model.clear()
            try:
                result = subprocess.run(["adb", "devices", "-l"], 
                                      capture_output=True, text=True, check=False)
                output = result.stdout.strip().splitlines()
                for line in output[1:]:
                    line = line.strip()
                    if not line or "offline" in line or "unauthorized" in line or "unknown" in line:
                        continue
                    parts = line.split()
                    serial = parts[0]
                    model = None
                    for p in parts:
                        if p.startswith("model:"):
                            model = p.split(":", 1)[1]
                            break
                    if not model:
                        model = "UnknownModel"
                    devices_by_model[model] = serial
                queue_log_message(f"Устройства обновлены: {devices_by_model}", "info")
            except Exception as e:
                queue_log_message(f"Ошибка при сканировании устройств: {e}", "error")
            
            model_list = list(devices_by_model.keys())
            signals.device_scan_completed.emit(model_list)
            
        thread = threading.Thread(target=worker, daemon=True)
        thread.start()
        
    def update_device_list(self, model_list):
        current_selection = self.load_last_model()
        self.device_combobox.clear()
        
        if model_list:
            self.device_combobox.addItems(model_list)
            if current_selection in model_list:
                self.device_combobox.setCurrentText(current_selection)
        else:
            self.device_combobox.addItem("Нет подключённых устройств")
            
    def refresh_wifi_async(self):
        def worker():
            global available_wifi_networks
            available_wifi_networks = get_available_networks()
            current_ssid = get_connected_wifi()
            signals.wifi_scan_completed.emit(available_wifi_networks, current_ssid)
            
        thread = threading.Thread(target=worker, daemon=True)
        thread.start()
        
    def update_wifi_list(self, networks, current_ssid):
        self.wifi_combobox.clear()
        if networks:
            self.wifi_combobox.addItems(networks)
            if current_ssid in networks:
                self.wifi_combobox.setCurrentText(current_ssid)
                
    def on_model_selected(self):
        model = self.device_combobox.currentText()
        self.save_last_model(model)
        
    def on_wifi_selected(self):
        global auto_paused_due_to_wifi
        selected = self.wifi_combobox.currentText()
        current_wifi = get_connected_wifi()
        
        if selected and selected != current_wifi:
            if is_processing and pause_event.is_set():
                pause_event.clear()
                auto_paused_due_to_wifi = True
                queue_log_message("Процесс приостановлен автоматически (Wi-Fi mismatch).", "info")
            if not is_processing:
                self.start_button.setEnabled(False)
        else:
            if not is_processing:
                self.start_button.setEnabled(True)
            else:
                if auto_paused_due_to_wifi and not pause_event.is_set():
                    pause_event.set()
                    auto_paused_due_to_wifi = False
                    queue_log_message("Процесс возобновлён автоматически (Wi-Fi выбран совпадает с текущей).", "info")
                    
    # Методы для работы с файлами настроек
    def get_limit_value(self):
        try:
            return int(self.limit_entry.text())
        except ValueError:
            return 0
            
    def save_last_limit(self):
        try:
            with open(last_limit_file, "w", encoding="utf-8") as f:
                f.write(self.limit_entry.text())
        except Exception as e:
            queue_log_message(f"Ошибка при сохранении лимита: {e}", "error")
            
    def load_last_limit(self):
        try:
            with open(last_limit_file, "r", encoding="utf-8") as f:
                return f.read().strip()
        except Exception:
            return "0"
            
    def load_hide_browser_state(self):
        try:
            with open(hide_browser_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return False
            
    def load_run_scenario_start_state(self):
        try:
            with open(run_scenario_start_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return False
            
    def load_run_scenario_end_state(self):
        try:
            with open(run_scenario_end_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return False
            
    def save_last_model(self, model):
        try:
            with open(last_model_file, "w", encoding="utf-8") as f:
                f.write(model)
        except Exception as e:
            queue_log_message(f"Ошибка при сохранении модели: {e}", "error")
            
    def load_last_model(self):
        try:
            with open(last_model_file, "r", encoding="utf-8") as f:
                return f.read().strip()
        except Exception:
            return ""
            
    # Методы для сохранения/загрузки состояния автопрокрутки
    def save_tokens_autoscroll_state(self):
        try:
            with open(tokens_autoscroll_state_file, "w", encoding="utf-8") as f:
                f.write(str(self.tokens_autoscroll_enabled).lower())
        except Exception as e:
            queue_log_message(f"Ошибка при сохранении состояния автопрокрутки токенов: {e}", "error")
            
    def load_tokens_autoscroll_state(self):
        try:
            with open(tokens_autoscroll_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return True  # По умолчанию включено
            
    def save_failed_autoscroll_state(self):
        try:
            with open(failed_autoscroll_state_file, "w", encoding="utf-8") as f:
                f.write(str(self.failed_autoscroll_enabled).lower())
        except Exception as e:
            queue_log_message(f"Ошибка при сохранении состояния автопрокрутки неуспешных логинов: {e}", "error")
            
    def load_failed_autoscroll_state(self):
        try:
            with open(failed_autoscroll_state_file, "r", encoding="utf-8") as f:
                return f.read().strip().lower() == "true"
        except Exception:
            return True  # По умолчанию включено
    
    def closeEvent(self, event):
        """Обработка закрытия приложения"""
        try:
            # Останавливаем рабочие потоки
            self.log_worker.stop()
            self.token_worker.stop()
            self.failed_worker.stop()
            
            # Ждем завершения потоков
            self.log_worker.wait(1000)  # максимум 1 секунда
            self.token_worker.wait(1000)
            self.failed_worker.wait(1000)
            
            # Сохраняем состояния чекбоксов
            try:
                with open(hide_browser_state_file, "w", encoding="utf-8") as f:
                    f.write(str(self.hide_browser_check.isChecked()).lower())
                with open(run_scenario_start_state_file, "w", encoding="utf-8") as f:
                    f.write(str(self.run_scenario_start_check.isChecked()).lower())
                with open(run_scenario_end_state_file, "w", encoding="utf-8") as f:
                    f.write(str(self.run_scenario_end_check.isChecked()).lower())
            except Exception as e:
                print(f"Ошибка при сохранении настроек: {e}")
                    
        except Exception as e:
            print(f"Ошибка при закрытии приложения: {e}")
        finally:
            event.accept()
            
    # Методы Selenium и обработки аккаунтов
    def open_browser(self, login, password):
        global successful_attempts, failed_attempts, batch_success_count
        global invalid_login_count, captcha_count, token_not_found_count
        global timeout_count, element_not_found_count, unexpected_error_count, sms_code_count

        start_time = time.time()
        queue_log_message(f"Открытие браузера для: {login}", "info")

        options = Options()
        options.add_argument("--incognito")
        if self.hide_browser_check.isChecked():
            options.add_argument("--headless")
            options.add_argument("--disable-gpu")
            queue_log_message("Скрытый режим браузера.", "info")

        service = Service(driver_path)
        driver = webdriver.Chrome(service=service, options=options)

        try:
            driver.get("https://vkhost.github.io/")
            queue_log_message("Страница vkhost открыта.", "info")
    
            # Ожидание полной загрузки страницы VK Host
            WebDriverWait(driver, 15).until(lambda d: d.execute_script('return document.readyState') == 'complete')
            queue_log_message("Полная загрузка страницы VK Host подтверждена.", "info")

            button = WebDriverWait(driver, 15).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, "button.btn[onclick='auth(2685278)']"))
            )
            button.click()
            queue_log_message("Кнопка авторизации нажата.", "info")

            WebDriverWait(driver, 15).until(lambda d: len(d.window_handles) > 1)
            driver.switch_to.window(driver.window_handles[-1])
            queue_log_message(f"Новая вкладка: {driver.current_url}", "info")
    
            # Ожидание полной загрузки новой вкладки
            WebDriverWait(driver, 15).until(lambda d: d.execute_script('return document.readyState') == 'complete')
            queue_log_message("Полная загрузка новой вкладки подтверждена.", "info")

            WebDriverWait(driver, 15).until(
                lambda d: d.current_url.startswith("https://id.vk.com/auth?")
            )
            queue_log_message(f"Страница ВК: {driver.current_url}", "info")

            # Новый шаг: Нажатие на второй элемент с указанным селектором перед вводом логина
            try:
                selector_elements = WebDriverWait(driver, 15).until(
                    EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".vkuiSegmentedControlOption__host.vkuiClickable__host.vkuiClickable__realClickable.vkuistyles__-focus-visible.vkuiRootComponent__host"))
                )
                if len(selector_elements) >= 2:
                    second_element = selector_elements[1]  # Второй элемент (индекс 1)
                    second_element.click()
                    queue_log_message("Нажат второй элемент по селектору перед вводом логина.", "info")
                else:
                    queue_log_message(f"Найдено меньше 2 элементов по селектору ({len(selector_elements)}), клик пропущен.", "error")
            except TimeoutException:
                queue_log_message("Timeout при поиске элементов для клика перед вводом логина.", "error")
            except Exception as e:
                queue_log_message(f"Ошибка при клике на второй селектор: {e}", "error")

            # Шаг 1: Ввод номера в поле name="login" с выделением содержимого перед вставкой
            login_field = WebDriverWait(driver, 15).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='login']"))
            )
            login_field.send_keys(Keys.CONTROL + 'a')  # Выделяем всё содержимое поля
            login_field.send_keys(login)  # Вставляем логин, перезаписывая выделенное
            time.sleep(0.5)  # Задержка после ввода логина
            queue_log_message("Логин вставлен после выделения поля.", "info")

            # Шаг 2: Нажатие на кнопку после ввода логина
            continue_button_login = WebDriverWait(driver, 15).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, ".vkuiInternalTappable.vkuiButton__host.vkuiButton__sizeL.vkuiButton__modePrimary.vkuiButton__appearanceAccent.vkuiButton__sizeYNone.vkuiButton__stretched.vkuiTappable__host.vkuiTappable__sizeXNone.vkuiTappable__hasPointerNone.vkuiClickable__host.vkuiClickable__realClickable.vkuistyles__-focus-visible.vkuiRootComponent__host"))
            )
            continue_button_login.click()
            queue_log_message("Кнопка после ввода логина нажата.", "info")
        
            # Ожидание полной загрузки после нажатия кнопки логина
            WebDriverWait(driver, 15).until(lambda d: d.execute_script('return document.readyState') == 'complete')
            queue_log_message("Полная загрузка после ввода логина подтверждена.", "info")

            # Шаг 3: Дождаться открытия окна с полем для пароля, SMS-кода или ошибки
            found_element = WebDriverWait(driver, 15).until(
                EC.any_of(
                    EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='password']")),  # Поле пароля
                    EC.presence_of_element_located((By.CSS_SELECTOR, ".vkc__ConfirmPhone__input")),  # Поле SMS-кода
                    EC.presence_of_element_located((By.CSS_SELECTOR, ".vkuiFormItem__bottom.vkuiFootnote__sizeYNone.vkuiFootnote__host.vkuiTypography__host.vkuiTypography__normalize.vkuiRootComponent__host"))  # Элемент ошибки
                )
            )

            element_class = found_element.get_attribute("class")
            if "vkuiFormItem__bottom" in element_class:
                error_text = "Невалидный логин или пароль."  # Ошибка после ввода логина
                invalid_login_count += 1
                failed_attempts += 1
                save_token_to_file(f"{error_text}: {login}", is_error=True)
                save_failed_login(login, password)
                append_full_result(login, password, error_text)
                queue_log_message(f"Ошибка после ввода логина: {error_text}", "error")
                queue_token_message(f"{error_text}: {login}")
                return  # Прерываем процесс, если ошибка найдена

            elif "vkc__ConfirmPhone__input" in element_class:
                error_text = "Код из SMS"  # Новая ошибка
                sms_code_count += 1
                failed_attempts += 1
                save_token_to_file(f"{error_text}: {login}", is_error=True)
                save_failed_login(login, password)
                append_full_result(login, password, error_text)
                queue_log_message(f"Ошибка: {error_text}", "error")
                queue_token_message(f"{error_text}: {login}")
                return  # Прерываем процесс, если требуется SMS-код

            # Если найдено поле пароля, продолжаем
            password_field = found_element
            password_field.send_keys(password)
            time.sleep(0.5)  # Задержка после ввода пароля
            queue_log_message("Пароль введён.", "info")

            # Шаг 4: Нажатие на кнопку после ввода пароля
            continue_button_password = WebDriverWait(driver, 15).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, ".vkuiInternalTappable.vkuiButton__host.vkuiButton__sizeL.vkuiButton__modePrimary.vkuiButton__appearanceAccent.vkuiButton__sizeYNone.vkuiButton__stretched.vkuiTappable__host.vkuiTappable__sizeXNone.vkuiTappable__hasPointerNone.vkuiClickable__host.vkuiClickable__realClickable.vkuistyles__-focus-visible.vkuiRootComponent__host"))
            )
            continue_button_password.click()
            queue_log_message("Кнопка после ввода пароля нажата.", "info")
        
            # Ожидание полной загрузки после нажатия кнопки пароля
            WebDriverWait(driver, 15).until(lambda d: d.execute_script('return document.readyState') == 'complete')
            queue_log_message("Полная загрузка после ввода пароля подтверждена.", "info")

            try:
                found_error = WebDriverWait(driver, 5).until(
                    EC.any_of(
                        EC.presence_of_element_located((By.CSS_SELECTOR, ".box_error")),
                        EC.presence_of_element_located((By.CSS_SELECTOR, ".oauth_captcha"))
                    )
                )

                error_class = found_error.get_attribute("class")
                if "box_error" in error_class:
                    error_text = "Невалидный логин или пароль."
                    invalid_login_count += 1
                elif "oauth_captcha" in error_class:
                    error_text = "Найдена капча"
                    captcha_count += 1
                else:
                    error_text = "Неизвестная ошибка"
                    unexpected_error_count += 1

                failed_attempts += 1
                save_token_to_file(f"{error_text}: {login}", is_error=True)
                save_failed_login(login, password)
                append_full_result(login, password, error_text)
                queue_log_message(f"Ошибка: {error_text}", "error")
                queue_token_message(f"{error_text}: {login}")
                return

            except TimeoutException:
                pass

            # Шаг 5: Ожидание страницы с токеном
            WebDriverWait(driver, 15).until(
                lambda d: d.current_url.startswith("https://oauth.vk.com/blank.html#access_token=")
            )
            url = driver.current_url
            queue_log_message(f"Страница с токеном открыта: {url}", "info")
        
            # Ожидание полной загрузки страницы с токеном
            WebDriverWait(driver, 15).until(lambda d: d.execute_script('return document.readyState') == 'complete')
            queue_log_message("Полная загрузка страницы с токеном подтверждена.", "info")

            # Шаг 6: Извлечение токена
            token_match = re.search(r"access_token=([^&]+)", url)
            if token_match:
                token = token_match.group(1)
                queue_log_message(f"Токен успешно извлечён: {token}", "success")
                save_token_to_file(token)
                append_full_result(login, password, token)
                successful_attempts += 1
                batch_success_count += 1
                queue_log_message("Токен получен и сохранён.", "success")

                current_limit = self.get_limit_value()
                if current_limit > 0 and batch_success_count >= current_limit:
                    self.run_flight_mode_scenario()
                    batch_success_count = 0
            else:
                failed_attempts += 1
                token_not_found_count += 1
                error_text = f"Токен не найден в URL: {url}"
                queue_log_message(error_text, "error")
                save_token_to_file(error_text, is_error=True)
                save_failed_login(login, password)
                append_full_result(login, password, "Токен не найден")
                queue_token_message(f"Токен не найден: {login}")

        except TimeoutException:
            failed_attempts += 1
            timeout_count += 1
            save_token_to_file(f"Timeout: {login}", is_error=True)
            save_failed_login(login, password)
            append_full_result(login, password, "Timeout")
            queue_log_message(f"Ошибка: Timeout. URL: {driver.current_url}", "error")
            queue_token_message(f"Timeout: {login}")

        except NoSuchElementException:
            failed_attempts += 1
            element_not_found_count += 1
            save_token_to_file(f"Элемент не найден: {login}", is_error=True)
            save_failed_login(login, password)
            append_full_result(login, password, "Элемент не найден")
            queue_log_message("Ошибка: Элемент не найден.", "error")
            queue_token_message(f"Элемент не найден: {login}")

        except Exception as e:
            failed_attempts += 1
            unexpected_error_count += 1
            save_token_to_file(f"{str(e)}: {login}", is_error=True)
            save_failed_login(login, password)
            append_full_result(login, password, f"Ошибка: {e}")
            queue_log_message(f"Неожиданная ошибка: {e}", "error")
            queue_token_message(f"Неожиданная ошибка: {login}")

        finally:
            driver.quit()
            queue_log_message("Браузер закрыт.", "info")
            signals.update_labels.emit()
            end_time = time.time()
            processing_time = end_time - start_time
            processing_time_formatted = format_time(processing_time)
            queue_log_message(f"Обработка аккаунта {login} завершена за {processing_time_formatted}.", "info")

    def run_flight_mode_scenario(self):
        chosen_model = self.device_combobox.currentText()
        if not chosen_model:
            queue_log_message("Не выбрана модель для сценария.", "error")
            return
            
        serial = devices_by_model.get(chosen_model)
        if not serial:
            queue_log_message(f"Не найден serial для '{chosen_model}'.", "error")
            return
            
        try:
            queue_log_message(f"Запуск сценария на '{chosen_model}'.", "info")
            d = u2.connect(serial)
            queue_log_message(f"Устройство: {d.device_info}", "info")
            
            subprocess.run(["adb", "-s", serial, "shell", "am", "start", "-a", "android.settings.AIRPLANE_MODE_SETTINGS"])
            time.sleep(0.3)
            
            switch_element = d(resourceId="android:id/switch_widget")
            switch_element.click_exists(timeout=3)
            time.sleep(0.3)
            switch_element.click_exists(timeout=3)
            time.sleep(0.5)
            
            d.app_start("com.android.settings", ".TetherSettings")
            time.sleep(0.3)
            if d(resourceId="com.android.settings:id/recycler_view").child(index=0).exists(timeout=2):
                d(resourceId="com.android.settings:id/recycler_view").child(index=0).click()
                time.sleep(0.3)
                
            for _ in range(3):
                d.press("back")
                time.sleep(0.2)
                
            queue_log_message("Ожидание Wi-Fi на ПК...", "info")
            while not is_connected():
                time.sleep(0.3)
            queue_log_message("Wi-Fi подключен.", "info")
            time.sleep(1)
            
        except Exception as e:
            queue_log_message(f"Ошибка сценария: {e}", "error")

# Вспомогательные функции
def append_full_result(login, password, result):
    all_results.append((login, password, result))

def add_token_separator():
    try:
        with file_lock:
            with open(tokens_file, "a", encoding="utf-8") as f:
                now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                separator = f"\n{'='*30} {now} {'='*30}\n"
                f.write(separator)
    except Exception as e:
        queue_log_message(f"Ошибка при добавлении разделителя: {e}", "error")

def save_token_to_file(token, is_error=False):
    try:
        with file_lock:
            with open(tokens_file, "a", encoding='utf-8') as file:
                if is_error:
                    file.write(f"Ошибка: {token}\n")
                else:
                    file.write(token + "\n")
        queue_log_message(f"{'Ошибка' if is_error else 'Токен'} сохранён: {tokens_file}", "info")
        if not is_error:
            queue_token_message(token)
    except Exception as e:
        queue_log_message(f"Ошибка сохранения токена: {e}", "error")

def save_failed_login(login, password):
    try:
        with file_lock:
            with open(failed_logins_file, "a", encoding='utf-8') as file:
                file.write(f"{login}:{password}\n")
        queue_log_message(f"Неуспешный логин сохранён: {failed_logins_file}", "info")
        queue_failed_message(login, password)
    except Exception as e:
        queue_log_message(f"Ошибка сохранения логина: {e}", "error")

def read_login_passwords():
    if not os.path.exists(login_pass_file):
        queue_log_message(f"Файл не найден: {login_pass_file}", "error")
        return []
    try:
        with open(login_pass_file, "r", encoding='utf-8') as file:
            accounts = [line.strip().split(":", 1) for line in file if line.strip()]
        return accounts
    except Exception as e:
        queue_log_message(f"Ошибка чтения файла: {e}", "error")
        return []

def is_connected():
    try:
        socket.create_connection(("8.8.8.8", 53), timeout=2)
        return True
    except OSError:
        return False

def get_connected_wifi():
    try:
        output = subprocess.check_output("netsh wlan show interfaces", shell=True, encoding="utf-8")
        for line in output.splitlines():
            if "SSID" in line and "BSSID" not in line:
                parts = line.split(":", 1)
                if len(parts) > 1:
                    ssid = parts[1].strip()
                    return ssid if ssid else "Нет подключения"
        return "Нет подключения"
    except Exception as e:
        return f"Ошибка: {e}"
        
def get_available_networks():
    networks = []
    try:
        output = subprocess.check_output("netsh wlan show networks", shell=True, encoding="utf-8")
        for line in output.splitlines():
            if "SSID" in line and ":" in line:
                parts = line.split(":", 1)
                if len(parts) > 1:
                    ssid = parts[1].strip()
                    if ssid and ssid not in networks:
                        networks.append(ssid)
    except Exception as e:
        queue_log_message(f"Ошибка при получении сетей: {e}", "error")
    return networks

def get_external_ip():
    try:
        response = requests.get('https://api.ipify.org', timeout=5)
        response.raise_for_status()
        return response.text.strip()
    except requests.RequestException:
        return "Недоступно"

def get_location_by_ip(ip):
    try:
        response = requests.get(f'https://ipinfo.io/{ip}/json', timeout=5)
        response.raise_for_status()
        data = response.json()
        city = data.get('city', 'Неизвестно')
        region = data.get('region', 'Неизвестно')
        country = data.get('country', 'Неизвестно')
        return f"{city}, {region}, {country}"
    except requests.RequestException:
        return "Недоступно"

def main():
    app = QApplication(sys.argv)
    
    # Установка стиля приложения
    app.setStyle('Fusion')
    
    window = MainWindow()
    window.show()
    
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
